<div class="tmd-doc">
<h1 class="tmd-header-1">
Explain Panic/Recover Mechanism in Detail
</h1>
<p></p>
<div class="tmd-usual">
Panic and recover mechanism has been <a href="control-flows-more.html#panic-recover">introduced before</a>, and several panic/recover use cases are shown in <a href="panic-and-recover-use-cases.html">the last article</a>. This current article will explain panic/recover mechanism in detail. Exiting phases of function calls will also be explained in detail.
</div>
<p></p>
<p></p>
<h3 id="exiting-phase" class="tmd-header-3">
Exiting Phases of Function Calls
</h3>
<p></p>
<div class="tmd-usual">
In Go, a function call may undergo an exiting phase before it fully exits. In the exiting phase, the deferred function calls pushed into the defer-call stack during executing the function call will be executed (in the inverse pushing order). When all of the deferred calls fully exit, the exiting phase ends and the function call also fully exits.
</div>
<p></p>
<div class="tmd-usual">
Exiting phases might also be called returning phases elsewhere.
</div>
<p></p>
<div class="tmd-usual">
A function call may enter its exiting phase (or exit directly) through three ways:
</div>
<ol class="tmd-list">
<li class="tmd-list-item">
<div class="tmd-usual">
after the call returns normally.
</div>
</li>
<li class="tmd-list-item">
<div class="tmd-usual">
when a panic occurs in the call.
</div>
</li>
<li class="tmd-list-item">
<div class="tmd-usual">
after the <code class="tmd-code-span">runtime.Goexit</code> function is called and fully exits in the call.
</div>
</li>
</ol>
<p></p>
<div class="tmd-usual">
For example, in the following code snippet,
</div>
<ul class="tmd-list">
<li class="tmd-list-item">
<div class="tmd-usual">
a call to the function <code class="tmd-code-span">f0</code> or <code class="tmd-code-span">f1</code> will enter its existing phase after it returns normally.
</div>
</li>
<li class="tmd-list-item">
<div class="tmd-usual">
a call to the function <code class="tmd-code-span">f2</code> will enter its exiting phase after the divided-by-zero panic happens.
</div>
</li>
<li class="tmd-list-item">
<div class="tmd-usual">
a call to the function <code class="tmd-code-span">f3</code> will enter its exiting phase after the <code class="tmd-code-span">runtime.Goexit</code> function call fully exits.
</div>
</li>
</ul>
<p></p>
<pre class="tmd-code line-numbers">
<code class="language-go">import (
	"fmt"
	"runtime"
)

func f0() int {
	var x = 1
	defer fmt.Println("exits normally:", x)
	x++
	return x
}

func f1() {
	var x = 1
	defer fmt.Println("exits normally:", x)
	x++
}

func f2() {
	var x, y = 1, 0
	defer fmt.Println("exits for panicking:", x)
	x = x / y // will panic
	x++       // unreachable
}

func f3() int {
	x := 1
	defer fmt.Println("exits for Goexiting:", x)
	x++
	runtime.Goexit()
	return x+x // unreachable
}
</code></pre>
<p></p>
<div class="tmd-usual">
BTW, the <code class="tmd-code-span">runtime.Goexit()</code> function is not intended to be called in the main goroutine of a program.
</div>
<p></p>
<h3 id="function-call-associations" class="tmd-header-3">
Associating Panics and Goexit Signals of Function Calls
</h3>
<p></p>
<div class="tmd-usual">
When a panic occurs directly in a function call, we say the (unrecovered) panic starts associating with the function call. Similarly, when the <code class="tmd-code-span">runtime.Goexit</code> function is called in a function call, we say a Goexit signal starts associating with the function call after the <code class="tmd-code-span">runtime.Goexit</code> call fully exits. As explained in the last section, associating either a panic or a Goexit signal with a function call will make the function call enter its exiting phase immediately.
</div>
<p></p>
<div class="tmd-usual">
We have learned that <a href="control-flows-more.html#panic-recover">panics can be recovered</a>. However, there are no ways to cancel a Goexit signal.
</div>
<p></p>
<p></p>
<div class="tmd-usual">
At any give time, a function call may associate with at most one unrecovered panic. If a call is associating with an unrecovered panic, then
</div>
<ul class="tmd-list">
<li class="tmd-list-item">
<div class="tmd-usual">
the call will associate with no panics when the unrecovered panic is recovered.
</div>
</li>
<li class="tmd-list-item">
<div class="tmd-usual">
when a new panic occurs in the function call, the new one will replace the old one to become the associating unrecovered panic of the function call.
</div>
</li>
</ul>
<p></p>
<div class="tmd-usual">
For example, in the following program, the recovered panic is panic 3, which is the last panic associating with the <code class="tmd-code-span">main</code> function call.
</div>
<p></p>
<pre class="tmd-code line-numbers">
<code class="language-go">package main

import "fmt"

func main() {
	defer func() {
		fmt.Println(recover()) // 3
	}()
	
	defer panic(3) // will replace panic 2
	defer panic(2) // will replace panic 1
	defer panic(1) // will replace panic 0
	panic(0)
}
</code></pre>
<p></p>
<div class="tmd-usual">
As Goexit signals can't be cancelled, arguing whether a function call may associate with at most one or more than one Goexit signal is unnecessary.
</div>
<p></p>
<div class="tmd-usual">
Although it is unusual, there might be multiple unrecovered panics coexisting in a goroutine at a time. Each one associates with one not-exited-yet function call in the call stack of the goroutine. When a nested call fully exits and it still associates with an unrecovered panic, the unrecovered panic will spread to the nesting call (the caller of the nested call). The effect is the same as a panic occurs directly in the nesting call. That says,
</div>
<ul class="tmd-list">
<li class="tmd-list-item">
<div class="tmd-usual">
if there was an old unrecovered panic associating with the nesting call before, the old one will be replaced by the spread one. For this case, the nesting call must had already entered its exiting phase for sure, so the next deferred function call in its defer-call stack will be invoked.
</div>
</li>
<li class="tmd-list-item">
<div class="tmd-usual">
if there was not an unrecovered panic associating with the nesting call before, the spread one will associates with the nesting call. For this case, the nesting call might have entered its exiting phase or not. If it hasn't, it will enter its exiting phase immediately.
</div>
</li>
</ul>
<p></p>
<div class="tmd-usual">
So, when a goroutine finishes to exit, there may be at most one unrecovered panic in the goroutine. If a goroutine exits with an unrecovered panic, the whole program crashes, and the information of the unrecovered panic will be reported. Otherwise, the goroutine exits normally (peacefully).
</div>
<p></p>
<div class="tmd-usual">
When a function is invoked, there is neither a panic nor Goexit signals associating with its call initially, no matter whether its caller (the nesting call) has entered exiting phase or not. Surely, panics might occur or the <code class="tmd-code-span">runtime.Goexit</code> function might be called later in the process of executing the call, so panics and Goexit signals might associate with the call later.
</div>
<p></p>
<div class="tmd-usual">
The following example program will crash if it runs, because the panic 2 is still not recovered when the new goroutine exits.
</div>
<p></p>
<pre class="tmd-code line-numbers">
<code class="language-go">package main

func main() {
	// The new goroutine.
	go func() {
		// This is an anonymous deferred call.
		// When it fully exits, the panic 2 will spread
		// to the entry function call of the new
		// goroutine, and replace the panic 0. The
		// panic 2 will never be recovered.
		defer func() {
			// As explained in the last example,
			// panic 2 will replace panic 1.
			defer panic(2)
			
			// When the anonymous function call fully
			// exits, panic 1 will spread to (and
			// associate with) the nesting anonymous
			// deferred call.
			func () {
				// Once the panic 1 occurs, there will
				// be two unrecovered panics coexisting
				// in the new goroutine. One (panic 0)
				// associates with the entry function
				// call of the new goroutine, the other
				// (panic 1) associates with the
				// current anonymous function call.
				panic(1)
			}()
		}()
		panic(0)
	}()
	
	select{}
}
</code></pre>
<p></p>
<div class="tmd-usual">
The output (when the above program is compiled with the standard Go compiler v1.25.n):
</div>
<pre class="tmd-code output">
panic: 0
	panic: 1
	panic: 2

...
</pre>
<p></p>
<div class="tmd-usual">
The format of the output is not perfect, it is prone to make some people think that the panic 0 is the final unrecovered panic, whereas the final unrecovered panic is actually panic 2.
</div>
<p></p>
<div class="tmd-usual">
Similarly, when a nested call fully exits and it is associating with a Goexit signal, then the Goexit signal will also spread to (and associate with) the nesting call. This will make the nesting call enter (if it hasn't entered) its exiting phase immediately.
</div>
<p></p>
<div class="tmd-usual">
When a Goexit signal associates with a function call, if the function call is associating with an unrecovered panic, then the panic will be recovered (honestly, it is weird to me). For example, the following program will exit peacefully, because the <code class="tmd-code-span">bye</code> panic will be recovered by the Goexit signal.
</div>
<p></p>
<pre class="tmd-code line-numbers">
<code class="language-go">package main

import (
	"runtime"
)

func f() {
	// The Goexit signal will disable the "bye" panic.
	defer runtime.Goexit()
	panic("bye")
}

func main() {
	go f()
	
	for runtime.NumGoroutine() &gt; 1 {
		runtime.Gosched()
	}
}
</code></pre>
<p></p>
<p></p>
<p></p>
<h3 id="some-recovers-are-no-ops" class="tmd-header-3">
Some <code class="tmd-code-span">recover</code> Calls Are No-Ops
</h3>
<p></p>
<div class="tmd-usual">
The builtin <code class="tmd-code-span">recover</code> function must be called at proper places to take effect. Otherwise, the calls are no-ops. For example, none of the <code class="tmd-code-span">recover</code> calls in the following example recover the <code class="tmd-code-span">bye</code> panic.
</div>
<p></p>
<pre class="tmd-code line-numbers">
<code class="language-go">package main

func main() {
	defer func() {
		defer func() {
			recover() // no-op
		}()
	}()
	defer func() {
		func() {
			recover() // no-op
		}()
	}()
	func() {
		defer func() {
			recover() // no-op
		}()
	}()
	func() {
		defer recover() // no-op
	}()
	func() {
		recover() // no-op
	}()
	recover()       // no-op
	defer recover() // no-op
	panic("bye")
}
</code></pre>
<p></p>
<div class="tmd-usual">
We have already known that the following <code class="tmd-code-span">recover</code> call takes effect.
</div>
<p></p>
<pre class="tmd-code line-numbers">
<code class="language-go">package main

func main() {
	defer func() {
		recover() // take effect
	}()

	panic("bye")
}
</code></pre>
<p></p>
<div class="tmd-usual">
Then why don't those <code class="tmd-code-span">recover</code> calls in the first example of the current section take effect? Let's read the current version of <a href="https://golang.org/ref/spec#Handling_panics">Go specification</a>:
</div>
<p></p>
<p></p>
<div class="tmd-quotation">
<div class="tmd-usual">
The return value of <code class="tmd-code-span">recover</code> is <code class="tmd-code-span">nil</code> if any of the following conditions holds:
</div>
<div class="tmd-base">
<ul class="tmd-list">
<li class="tmd-list-item">
<div class="tmd-usual">
<code class="tmd-code-span">panic</code>'s argument was nil;
</div>
</li>
<li class="tmd-list-item">
<div class="tmd-usual">
the goroutine is not panicking;
</div>
</li>
<li class="tmd-list-item">
<div class="tmd-usual">
<code class="tmd-code-span">recover</code> was not called directly by a deferred function.
</div>
</li>
</ul>
</div>
</div>
<p></p>
<div class="tmd-usual">
There is <a href="panic-and-recover-use-cases.html#avoid-verbose">an example</a> showing the first condition case in the last article.
</div>
<p></p>
<p></p>
<div class="tmd-usual">
Most of the <code class="tmd-code-span">recover</code> calls in the first example of the current section satisfy either the second or the third conditions mentioned in Go specification, except the first call. Yes, here, the current descriptions are not precise yet. The third condition should be described as
</div>
<p></p>
<ul class="tmd-list">
<li class="tmd-list-item">
<div class="tmd-usual">
<code class="tmd-code-span">recover</code> was not called directly by a deferred function <span class="tmd-bold">call which was called directly by the function call associating with the expected to-be-recovered panic</span>.
</div>
</li>
</ul>
<p></p>
<div class="tmd-usual">
In the first example of the current section, the expected to-be-recovered panic is associating with the <code class="tmd-code-span">main</code> function call. The first <code class="tmd-code-span">recover</code> call is called directly by a deferred function call but the deferred function call is not called directly by the <code class="tmd-code-span">main</code> function call. This is why the first <code class="tmd-code-span">recover</code> call is a no-op.
</div>
<p></p>
<div class="tmd-usual">
In fact, the current Go specification also doesn't explain well why the second <code class="tmd-code-span">recover</code> call (by code line order), which is expected to recover panic 1, in the following example doesn't take effect.
</div>
<p></p>
<pre class="tmd-code line-numbers">
<code class="language-go">// This program exits without recovering panic 1.
package main

func demo() {
	defer func() {
		defer func() {
			recover() // this one recovers panic 2
		}()

		defer recover() // no-op

		panic(2)
	}()
	panic(1)
}

func main() {
	demo()
}
</code></pre>
<p></p>
<div class="tmd-usual">
What Go specification doesn't mention is that, every <code class="tmd-code-span">recover</code> call is viewed as an attempt to recover the newest unrecovered panic in the current goroutine.
</div>
<p></p>
<div class="tmd-usual">
Go runtime thinks the second <code class="tmd-code-span">recover</code> call in the above example attempts to recover the newest unrecovered panic, panic 2, which is associating with the caller call of the second <code class="tmd-code-span">recover</code> call. The second <code class="tmd-code-span">recover</code> call is not called directly by a deferred function call which is called by the associating function call. Instead, it is directly called by the associating function call. This is why the second <code class="tmd-code-span">recover</code> call is a no-op.
</div>
<p></p>
<h3 id="summary" class="tmd-header-3">
Summary
</h3>
<p></p>
<div class="tmd-usual">
OK, now, let's try to make a short description on which <code class="tmd-code-span">recover</code> calls will take effect:
</div>
<div class="tmd-base alert alert-warning">
<div class="tmd-usual">
A <code class="tmd-code-span">recover</code> call takes effect only if the direct caller of the <code class="tmd-code-span">recover</code> call is a deferred call and the direct caller of the deferred call associates with the newest unrecovered panic in the current goroutine. An effective <code class="tmd-code-span">recover</code> call disassociates the newest unrecovered panic from its associating function call, and returns the value passed to the <code class="tmd-code-span">panic</code> call which produced the newest unrecovered panic.
</div>
</div>
<p></p>
<p></p>
</div>
